﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Diagnostics;
using System.Security.Principal;

namespace MessageAPI
{
    class UdpState
    {
        public UdpClient u;
        public IPEndPoint e;
    }
    public class UdpMessage
    {
        public UdpUser IntendedUser = new UdpUser();
        public UdpUser SenderUser = new UdpUser();
        public string Message = "";
        public udpMessageType MessageType;

        public enum udpMessageType
        {
            Unkown = 0,
            WebsiteShare = 1,
            Chat = 2
        }

        public const string Anyone = "ANY";

        public UdpMessage()
        {
        }
        public UdpMessage(string user, string userMessage)
        {
            setupUser(user, userMessage, udpMessageType.Unkown);
        }
        public UdpMessage(string user, string userMessage, udpMessageType type)
        {
            setupUser(user, userMessage, type);
        }
		public UdpMessage(UdpUser user, string userMessage, udpMessageType type)
		{
			IntendedUser = user;
			Message = userMessage;
			MessageType = type;
		}

        private void setupUser(string user, string userMessage, udpMessageType type)
        {
            IntendedUser = new UdpUser(user);
            Message = userMessage;
            MessageType = type;
        }

        public static udpMessageType messageTypeFromString(string type)
        {
            switch (type)
            {
                case "1":
                    return udpMessageType.WebsiteShare;
                case "2":
                    return udpMessageType.Chat;
                default:
                    return udpMessageType.Unkown;
            }
        }
    }

    public class UdpUser
    {
        public string UserName = "";
		public string Alias = "";
        public const string Anyone = "ANY";
        public const string Self = "SELF";
        public UdpUser()
        {
        }
        public UdpUser(string name)
        {
            UserName = name;
        }
		public UdpUser(string name, string alias)
		{
			UserName = name;
			Alias = alias;
		}
    }

    public class Broadcast_Service
    {
        #region "Event Stuff"
        public delegate void MessageSuccess(object sender);
        public delegate void MessageFail(object sender, int errorCode);
        public delegate void MessageRecieved(object sender, MessageAPI.UdpMessage message);
        public delegate void ServiceStarted(object sender);

        public event MessageSuccess OnMessageSuccess;
        public event MessageFail OnMessageFail;
        public event MessageRecieved OnMessageRecieved;
        public event ServiceStarted OnServiceStarted;

        #endregion

        #region "Network Stuff"
        const int defaultPort = 5683;
        const string defaultIP = "255.255.255.255";

        private int currentPort;
        private string currentIP;

        private UdpClient client = new UdpClient();
        UdpState s = new UdpState();
        #endregion

        Thread recieveThread;

        public bool onlyMyMessages = true;

        char controlChar = System.Text.Encoding.UTF8.GetChars(new byte[] { 144 })[0];

        #region "Constructors"
        public Broadcast_Service()
        {
            setupService(defaultIP, defaultPort);
        }

        public Broadcast_Service(int port)
        {
            setupService(defaultIP, port);
        }

        public Broadcast_Service(string IP)
        {
            setupService(IP, defaultPort);
        }

        public Broadcast_Service(string IP, int port)
        {
            setupService(IP, port);
        }
        #endregion

        private void setupService(string IP, int port)
        {
            currentPort = port;
            currentIP = IP;
            client.EnableBroadcast = true;
        }

        public void startService()
        {
            client = new UdpClient();
            client.ExclusiveAddressUse = false;
            client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            client.Client.Bind(new IPEndPoint(IPAddress.Any, currentPort));
            //client = new UdpClient(currentPort, AddressFamily.InterNetwork);
            //client.JoinMulticastGroup(IPAddress.Parse(currentIP));
                        
            s.e = new IPEndPoint(IPAddress.Parse(currentIP), currentPort);
            s.u = client;
            Thread recieveThread = new Thread(RecieveLoop);
            recieveThread.Start();
            try
            {
                OnServiceStarted(this);
            }
            catch (NullReferenceException ex)
            {
            }
        }

        public void destroy()
        {
            shuttingDown = true;
            if (recieveThread != null)
            {
                recieveThread.Abort();
            }
            // This was causing a DisposedObject Exception so I just commented it out
            // client.Close();
        }

        ~Broadcast_Service()
        {
            destroy();
        }

        public Boolean newmessage = true;
        public Boolean shuttingDown = false;

        private void RecieveLoop()
        {
            try
            {
                while (shuttingDown == false)
                {
                    if (newmessage == true)
                    {
                        client.BeginReceive(new AsyncCallback(ReceiveCallback), s);
                        newmessage = false;
                    }
                    else
                    {
                        Thread.Sleep(500);
                    }
                };
                Debug.WriteLine("Thread Exiting");
            }
            catch (ThreadAbortException ex)
            {
                Debug.WriteLine("Thread Aborting..." + ex.Message);
                return;
            }
        }

        public void ReceiveCallback(IAsyncResult ar)
        {
            UdpClient u = (UdpClient)((UdpState)(ar.AsyncState)).u;
            IPEndPoint e = (IPEndPoint)((UdpState)(ar.AsyncState)).e;

            Byte[] receiveBytes = u.EndReceive(ar, ref e);
            string receiveString = Encoding.UTF8.GetString(receiveBytes);

            newmessage = true;
            Debug.WriteLine("Message Received!");
            UdpMessage UserMessage = parseMessage(receiveString);
            if (onlyMyMessages == true)
            {
                if (UserMessage.IntendedUser.UserName == System.Security.Principal.WindowsIdentity.GetCurrent().Name || UserMessage.IntendedUser.UserName == UdpUser.Anyone)
                {
                    try
                    {
                        OnMessageRecieved(this, UserMessage);
                    }
                    catch (NullReferenceException ex)
                    {
                    }
                }
            }
            else
            {
                try
                {
                    OnMessageRecieved(this, UserMessage);
                }
                catch (NullReferenceException ex)
                {
                }
            }

        }

        public void sendMessage(string message, UdpMessage.udpMessageType type)
        {
            UdpMessage UserMessage = new UdpMessage(UdpUser.Anyone, message, type);
            sendMessage(UserMessage);
        }

        public void sendMessage(UdpMessage message, string alias)
        {
            message.SenderUser.Alias = alias;
            sendMessage(message);
        }
        public void sendMessage(UdpMessage message)
        {
            IPEndPoint EndPoint = new IPEndPoint(IPAddress.Broadcast, currentPort);
            try
            {
                Debug.WriteLine("Sending Message to " + message.IntendedUser.UserName);
                message.SenderUser.UserName = WindowsIdentity.GetCurrent().Name;
                byte[] messageBytes = System.Text.Encoding.UTF8.GetBytes(message.IntendedUser.UserName + controlChar + 
				                                                         message.SenderUser.UserName + controlChar + 
				                                                         message.SenderUser.Alias + controlChar  +
				                                                         (int)message.MessageType + controlChar + 
				                                                         message.Message);
                client.Send(messageBytes, messageBytes.Length, EndPoint);
                try
                {
                    OnMessageSuccess(this);
                }
                catch (NullReferenceException ex)
                {
                }
            }
            catch (System.Net.Sockets.SocketException ex)
            {
                try
                {
                    OnMessageFail(this, ex.ErrorCode);
                }
                catch (NullReferenceException nex)
                {
                }
            }
        }

        private UdpMessage parseMessage(string message)
        {
            Debug.WriteLine("Parsing Message: " + message);
            message = message.Trim();
            Debug.WriteLine("Message Trimmed");
            int username = 0;
            int sendername = 0;
			int alias = 0;
            bool foundUsername = false;
            bool foundSenderName = false;
			bool foundAliasName = false;
            foreach (char x in message.ToCharArray())
            {
                if (x == controlChar)
                {
                    if (foundUsername == false)
                    {
                        foundUsername = true;
                    } else if (foundSenderName == false)
                    {
                        foundSenderName = true;
                    } else if (foundAliasName == false)
					{
						foundAliasName = true;
					}
                    else
                    {
                        break;
                    }
                }
                else
                {
                    if(foundUsername == false) {
                        username += 1;
                    }
                    else if (foundSenderName == false)
                    {
                        sendername += 1;
                    } else if (foundAliasName == false)
					{
						alias += 1;
					}
                }
            }
            Debug.WriteLine("Username is: " + username + ", Alias length is:" + alias + ", Message Length is: " + message.Length);
            UdpMessage newMessage = new UdpMessage();
            newMessage.IntendedUser = new UdpUser(message.Substring(0, username));
            newMessage.SenderUser = new UdpUser(message.Substring(username + 1, sendername), message.Substring(username + sendername + 2, alias));
            newMessage.MessageType = UdpMessage.messageTypeFromString(message.Substring((username + sendername + alias) + 3, 1));
            newMessage.Message = message.Substring((username + sendername + alias) + 5, (message.Length - ((username + sendername + alias) + 5)));
            Debug.WriteLine(newMessage.IntendedUser.UserName + ":" + newMessage.SenderUser.UserName +":" + newMessage.SenderUser.Alias + ":" + newMessage.MessageType + ":" + newMessage.Message);
            return newMessage;
        }
    }

    
}
